﻿// ———————————————————————–
// <copyright file="CAD.cs" company="The MITRE Corporation">
//    Copyright (c) 2014 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>
// ———————————————————————–
/////////////////////////////////////////////////////////////////////////////////////////////////
// CAD.cs:
//
// This is the top level element.  To create a CAD message instantiate a new CAD object
//
// Project: IC.NET - CAD Library
// Author:  Jim Cronin - MITRE
/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2014 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)

using System;
using System.Collections.Generic;
using System.IO;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;
using EDXLSharp.NIEMCore;
using System.Linq;

namespace EDXLSharp.NIEMCADLib
{
  /// <summary>
  /// A CAD message
  /// </summary>
  public class CADEvent : IGeoRSS, IContentObject
  {
    /// <summary>
    /// Unique identifier associated with the subject of the message
    /// (resource, incident, etc)
    /// </summary>
    public string EventID { get; set; }

    /// <summary>
    /// The <see cref="EDXLSharp.NIEMCADLib.EventTypeDescriptor"/> for this message
    /// </summary>
    public EventTypeDescriptor EventTypeDescriptor { get; set; }

    /// <summary>
    /// The Location of the Event
    /// </summary>
    public EventLocation EventLocation { get; set; }

    /// <summary>
    /// The <see cref="EDXLSharp.NIEMCADLib.EventValidityDateTimeRange"/> for which this message is valid
    /// </summary>
    public EventValidityDateTimeRange ValidityRange { get; set; }

    /// <summary>
    /// DateTime this message was created
    /// </summary>
    public DateTime MessageDate { get; set; }

    /// <summary>
    /// The location of the event, represented using the United States Nation Grid (USNG)
    /// </summary>
    public USNGCoordinate USNGCoordinate { get; set; }

    /// <summary>
    /// Information about linked events
    /// </summary>
    public ICollection<EventLink> EventLinks { get; set; }

    public ICollection<EventComment> Comments { get; set; }

    /// <summary>
    /// The subject of the CAD message (incident or resource)
    /// </summary>
    public Detail Detail { get; set; }

    /// <summary>
    /// A community-specific extension to a message
    /// </summary>
    public EventExtensions Extension { get; set; }

    public CADEvent(string eventID, EventTypeDescriptor eventCategory, EventLocation eventLocation, EventValidityDateTimeRange eventValidityRange, IEnumerable<EventLink> linkedEvents, IncidentDetail incident)
      : this(eventID, eventCategory, eventLocation, eventValidityRange, DateTime.Now, linkedEvents, incident)
    {
    }

    public CADEvent(string eventID, EventTypeDescriptor eventCategory, EventLocation eventLocation, EventValidityDateTimeRange eventValidityRange, IEnumerable<EventLink> linkedEvents, ResourceDetail resource)
      : this(eventID, eventCategory, eventLocation, eventValidityRange, DateTime.Now, linkedEvents, resource)
    {
    }

    public CADEvent(string eventID, EventTypeDescriptor eventCategory, EventLocation eventLocation, EventValidityDateTimeRange eventValidityRange, DateTime messageDate, IEnumerable<EventLink> linkedEvents, IncidentDetail incident)
      : this(eventID, eventCategory, eventLocation, eventValidityRange, messageDate, linkedEvents)
    {
      Detail = incident;
    }

    public CADEvent(string eventID, EventTypeDescriptor eventCategory, EventLocation eventLocation, EventValidityDateTimeRange eventValidityRange, DateTime messageDate, IEnumerable<EventLink> linkedEvents, ResourceDetail resource)
      : this(eventID, eventCategory, eventLocation, eventValidityRange, messageDate, linkedEvents)
    {
      Detail = resource;
    }

    private CADEvent(string eventID, EventTypeDescriptor eventCategory, EventLocation eventLocation, EventValidityDateTimeRange eventValidityRange, DateTime messageDate, IEnumerable<EventLink> linkedEvents)
      : this()
    {
      EventID = eventID;
      EventTypeDescriptor = eventCategory;
      EventLocation = eventLocation;
      ValidityRange = eventValidityRange;
      MessageDate = messageDate;
      EventLinks = linkedEvents.ToList();
    }

    /// <summary>
    /// Initializes a new instance of the CAD class
    /// </summary>
    public CADEvent()
    {
      EventLinks = new List<EventLink>();
      Comments = new List<EventComment>();
    }

    /// <summary>
    /// Initializes a new instance of the CAD class from a String
    /// </summary>
    /// <param name="xmlString">Valid XML CAD Message Data in a String</param>
    /// <exception cref="System.ArgumentException">Thrown if xmlString is null, empty, whitespace, or invalid XML</exception>
    public CADEvent(string xmlString) : this()
    {
      if(string.IsNullOrWhiteSpace(xmlString))
      {
        throw new ArgumentException("XML must not be null or whitespace", "xmlString");
      }

      XmlDocument doc = new XmlDocument();
      try
      {
        doc.LoadXml(xmlString);
      }
      catch (XmlException xe)
      {
        throw new ArgumentException("Error parsing XML. See inner exception for details", xe);
      }

      this.ReadXML(doc.DocumentElement);
    }

    /// <summary>
    /// Interface to Allow Object to be Serialized to Geo-RSS
    /// </summary>
    /// <exception cref="System.ArgumentNullException">Thrown if items or linkUID is null</exception>
    /// <exception cref="System.InvalidOperationException">Thrown if this CAD Event is in an invalid state to generate GeoRSS</exception>
    public void ToGeoRSS(List<SyndicationItem> items, Guid linkUID)
    {
      if (items == null)
      {
        throw new ArgumentNullException("Syndication Items List Can't Be Null");
      }

      if (linkUID == null)
      {
        throw new ArgumentNullException("LinkUID Can't Be Null");
      }

      SyndicationItem myitem = new SyndicationItem();

      OrganizationType owningOrg;
      if (this.Detail is IncidentDetail)
      {
        owningOrg = ((IncidentDetail)this.Detail).OwningOrganization;
        myitem.Categories.Add(new SyndicationCategory("CAD Incident", EDXLConstants.SyndicationCategoryScheme, EDXLConstants.SyndicationCategoryLabel));
      }
      else if (this.Detail is ResourceDetail)
      {
        owningOrg = ((ResourceDetail)this.Detail).OwningOrganization;
        myitem.Categories.Add(new SyndicationCategory("CAD Resource", EDXLConstants.SyndicationCategoryScheme, EDXLConstants.SyndicationCategoryLabel));
      }
      else
      {
        throw new InvalidOperationException("CADEvent.Detail is null or is not an Incident or Resource");
      }
      myitem.Authors.Add(new SyndicationPerson(owningOrg.OrganizationIdentification.ID, "Originating Organization", String.Empty));

      this.WriteGeoRSSGML(myitem);
      myitem.Copyright = new TextSyndicationContent(EDXLConstants.SyndicationCopyright);
      //item.LastUpdatedTime = this.LastUpdated;
      myitem.Links.Add(new SyndicationLink(new Uri(EDXLConstants.SyndicationLinkBaseURI + "HTML/" + linkUID.ToString())));
      myitem.PublishDate = DateTime.Now;

      myitem.ElementExtensions.Add("expire_time", "", this.ValidityRange.EndDate.ToUniversalTime().ToString());

      // TODO: CADEvent.ToGeoRSS this should be the specific Incident or Resource type...once I can determine what the subject is
      //string summary = this.cotevent.Type;

      //TODO: CADEvent.ToGeoRSS Update this once I can determine Incident or Resource
      //ICotDetailComponent details = this.cotevent.Detail.GetFirstElement("_EMTasking");
      //if (details != null)
      //{
      //  XmlNode node = details.XmlNode.SelectSingleNode("Address");
      //  if (node != null)
      //  {
      //    summary = node.InnerText;
      //  }
      //}
      //else
      //{
      //  ICotDetailComponent remarks = this.cotevent.Detail.GetFirstElement("remarks");
      //  if (remarks != null && remarks.XmlNode != null)
      //  {
      //    summary = remarks.XmlNode.InnerText;
      //  }

      //  if (string.IsNullOrWhiteSpace(summary))
      //  {
      //    summary = this.cotevent.Type;
      //  }
      //}

      //myitem.Summary = new TextSyndicationContent(summary);

      //TODO: CADEvent.ToGeoRSS determine what id to use, UUID, Owning Id with Owning Org Id?, ....
      //ICotDetailComponent detailsUID = this.cotevent.Detail.GetFirstElement("uid");
      //if (detailsUID != null && detailsUID.XmlNode != null)
      //{
      //  XmlAttribute fvcotDetailsUID = detailsUID.XmlNode.Attributes["fvcot"];
      //  XmlAttribute icnetDetailsUID = detailsUID.XmlNode.Attributes["icnet"];

      //  if (fvcotDetailsUID != null)
      //  {
      //    myitem.Title = new TextSyndicationContent(fvcotDetailsUID.Value + " (CoT)");
      //  }
      //  else if (icnetDetailsUID != null)
      //  {
      //    myitem.Title = new TextSyndicationContent(icnetDetailsUID.Value + " (CoT)");
      //  }
      //  else
      //  {
      //    myitem.Title = new TextSyndicationContent(this.cotevent.Uid + " (CoT)");
      //  }
      //}
      //else
      //{
      //  myitem.Title = new TextSyndicationContent(this.cotevent.Uid + " (CoT)");
      //}


      StringBuilder contentstr = new StringBuilder();

      //TODO: CADEvent.ToGeoRSS determine what type to use
      //item.Content = new TextSyndicationContent(this.cotevent.Type);

      items.Add(myitem);
    }

    /// <summary>
    /// Sets the Content Keyword ValueListName and Values
    /// </summary>
    /// <param name="ckw">ContentKeywork ValueList</param>
    /// <returns>Content Description String</returns>
    /// <exception cref="System.ArgumentNullException">Thrown if ckw is null</exception>

    public DateTime ExpiresDateTime
    {
      get { return ValidityRange.EndDate; }
    }
    
    public string SetContentKeywords(ValueList ckw)
    {
      if (ckw == null)
      {
        throw new ArgumentNullException("ckw");
      }

      ckw.ValueListURN = EDXLConstants.ContentKeywordListName;
      ckw.Value.Add("NIEM-CAD");

      return "NIEM-CAD";
    }

    /// <summary>
    /// Validates The Current DE Object Against The XSD Schema File
    /// </summary>
    /// <param name="xmldata">XML String To Parse and Validate</param>
    public void ValidateToSchema()
    {
      //TODO: CADEvent.ValidateToSchema()
    }

    /// <summary>
    /// Writes this Object to an inline XML Document
    /// </summary>
    /// <param name="xwriter">Valid XMLWriter</param>
    /// <exception cref="System.ArgumentNullException">Thrown if xwriter is null</exception>
    /// <exception cref="EDXLSharp.ValidationException">Thrown if the object fails to validate</exception>
    public void WriteXML(XmlWriter xwriter)
    {
      if (xwriter == null)
      {
        throw new ArgumentNullException("xwriter");
      }

      this.Validate();

      string cadPrefix = EDXLConstants.CADPrefix;
      string cadNamespace = EDXLConstants.CADNamespace;
      string niemCorePrefix = EDXLConstants.NiemCorePrefix;
      string niemCoreNamespace = EDXLConstants.NiemCoreNamespace;
      string milopsPrefix = EDXLConstants.MilopsPrefix;
      string milopsNamespace = EDXLConstants.MilopsNamespace;
      
      xwriter.WriteStartElement(cadPrefix, "Event", cadNamespace);

      //Event Id
      xwriter.WriteStartElement(milopsPrefix, "EventID", milopsNamespace);
      xwriter.WriteElementString(niemCorePrefix, "IdentificationID", niemCoreNamespace, this.EventID);
      xwriter.WriteEndElement();

      //Event Category
      if (this.EventTypeDescriptor != null)
      {
        this.EventTypeDescriptor.WriteXML(xwriter);
      }

      //Event LocationExtension
      if (this.EventLocation != null)
      {
        this.EventLocation.WriteXML(xwriter);
      }

      //Event Message Validity Date Time Range
      if (this.ValidityRange != null)
      {
        this.ValidityRange.WriteXML(xwriter);
      }

      //Event Message Date Time
      xwriter.WriteStartElement(milopsPrefix, "EventMessageDateTime", milopsNamespace);
      xwriter.WriteElementString(niemCorePrefix, "DateTime", niemCoreNamespace, this.MessageDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.ffZ"));
      xwriter.WriteEndElement();
      
      //USNG Coordinate
      if (USNGCoordinate != null)
      {
        USNGCoordinate.WriteXML(xwriter);
      }

      //Event Links
      foreach(EventLink link in this.EventLinks)
      {
        link.WriteXML(xwriter);
      }

      //Event Commments
      foreach (EventComment comment in this.Comments)
      {
        comment.WriteXML(xwriter);
      }

      if (Detail != null)
      {
        Detail.WriteXML(xwriter);
      }

      if (this.Extension != null)
      {
        this.Extension.WriteXML(xwriter);
      }

      xwriter.WriteEndElement();//</cad:Event>
    }

    /// <summary>
    /// Reads this Object's values from an XML NodeList
    /// </summary>
    /// <param name="rootnode">Root XML Node of the Object Element</param>
    /// <exception cref="System.ArgumentNullException">Thrown if rootnode is null</exception>
    /// <exception cref="System.ArgumentException">Thrown if rootnode is not a CAD Event</exception>
    /// <exception cref="EDXLSharp.ValidationException">Thrown if the object fails to validate</exception>
    public void ReadXML(XmlNode rootnode)
    {
      if (rootnode == null)
      {
        throw new ArgumentNullException("rootnode");
      }

      if (rootnode.LocalName != "Event")
      {
        throw new ArgumentException("No Event element found!");
      }

      foreach (XmlNode node in rootnode.ChildNodes)
      {
        if (string.IsNullOrEmpty(node.InnerText))
        {
          continue;
        }

        switch (node.LocalName)
        {
          case "EventID":
            this.EventID = node.InnerText;
            break;
          case "EventTypeDescriptor":
            this.EventTypeDescriptor = new EventTypeDescriptor();
            this.EventTypeDescriptor.ReadXML(node);
            break;
          case "EventLocation":
            this.EventLocation = new EventLocation();
            this.EventLocation.ReadXML(node);
            break;
          case "EventValidityDateTimeRange":
            this.ValidityRange = new EventValidityDateTimeRange();
            this.ValidityRange.ReadXML(node);
            break;
          case "EventMessageDateTime":
            this.MessageDate = DateTime.Parse(node.InnerText);
            break;
          case "USNGCoordinate":
            this.USNGCoordinate = new USNGCoordinate();
            this.USNGCoordinate.ReadXML(node);
            break;
          case "EventLink":
            EventLink link = new EventLink();
            link.ReadXML(node);
            this.EventLinks.Add(link);
            break;
          case "IncidentDetail":
            this.Detail = new IncidentDetail();
            this.Detail.ReadXML(node);
            break;
          case "ResourceDetail":
            this.Detail = new ResourceDetail();
            this.Detail.ReadXML(node);
            break;
          case "EventComment":
            EventComment comment = new EventComment();
            comment.ReadXML(node);
            this.Comments.Add(comment);
            break;
          case "EventCommunityExtensions":
            Extension = new EventExtensions();
            Extension.ReadXML(node);
            break;
          default:
            throw new ArgumentException("Unexpected element " + node.LocalName + " in Event");
        }
      }

      this.Validate();
    }

    /// <summary>
    /// Validates the current CAD object
    /// </summary>
    /// <exception cref="EDXLSharp.ValidationException">Thrown if validation fails</exception>
    public void Validate()
    {
      if (string.IsNullOrWhiteSpace(this.EventID))
      {
        throw new ValidationException("Event Id is missing");
      }
      
      if (this.EventTypeDescriptor == null)
      {
        throw new ValidationException("Event Category is missing");
      }
      this.EventTypeDescriptor.Validate();

      if (this.EventLocation == null)
      {
        throw new ValidationException("Event LocationExtension is missing");
      }
      this.EventLocation.Validate();

      if (this.ValidityRange == null)
      {
        throw new ValidationException("Event Validity timeframe is missing");
      }
      this.ValidityRange.Validate();

      if (this.MessageDate == DateTime.MinValue)
      {
        throw new ValidationException("Event MessageDate must be set");
      }

      if (USNGCoordinate != null)
      {
        USNGCoordinate.Validate();
      }

      foreach (EventLink link in this.EventLinks)
      {
        link.Validate();
      }

      if (this.Detail == null)
      {
        throw new ValidationException("CAD Detail is missing");
      }
      this.Detail.Validate();
    }

    /// <summary>
    /// Writes this CAD Object to GeoRSS GML
    /// </summary>
    /// <param name="item">Sydication Item to Convert to GML</param>
    private void WriteGeoRSSGML(SyndicationItem item)
    {
      StringBuilder output = new StringBuilder();
      XmlWriterSettings xsettings = new XmlWriterSettings();
      xsettings.Indent = true;
      xsettings.IndentChars = "\t";
      xsettings.OmitXmlDeclaration = true;
      XmlWriter xwriter = XmlWriter.Create(output, xsettings);
      xwriter.WriteStartElement(EDXLConstants.GeoRSSPrefix, "where", EDXLConstants.GeoRSSNamespace);
      xwriter.WriteStartElement(EDXLConstants.GMLPrefix, "Point", EDXLConstants.GML32Namespace);
      //TODO: CADEvent.WriteGeoRSSGML FIX this 
      //riter.WriteElementString(EDXLConstants.GMLPrefix, "pos", EDXLConstants.GML32Namespace, this.EventLocation.LocationGeospatialCoordinate..Latitude.ToString() + " " + this.EventLocation.LocationGeospatialCoordinate.Longitude.ToString());
      xwriter.WriteEndElement();
      xwriter.WriteEndElement();
      xwriter.Flush();
      xwriter.Close();
      MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(output.ToString()));
      item.ElementExtensions.Add(XmlReader.Create(ms));
    }

    public override string ToString()
    {
      using (StringWriter sw = new StringWriter())
      {
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
        using (XmlWriter xwriter = XmlWriter.Create(sw, settings))
        {
          this.WriteXML(xwriter);
        }
        return sw.ToString();
      }
    }
  }
}